package cz.data.platform.web;

import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import cz.data.common.annotation.DataInner;
import cz.data.common.core.DataGrantedAuthority;
import cz.data.common.core.DataRole;
import cz.data.common.core.DataUser;
import cz.data.common.support.YamlPropertySourceFactory;
import cz.data.common.jackson.mixin.DataGrantedAuthorityMixin;
import cz.data.common.jackson.mixin.DataRoleMixin;
import cz.data.common.jackson.mixin.DataUserMixin;
import cz.data.common.support.security.AnnotationMatcherRule;
import cz.data.common.support.security.DynamicRequestMatcherContainer;
import cz.data.common.support.security.DiscoveryServiceHeaderFactory;
import cz.data.common.support.security.DynamicRequestMatcherRule;
import cz.data.common.utils.SecurityModuleUtil;
import cz.data.domain.system.rpc.LogServiceFeign;
import cz.data.platform.web.aspectj.LogAspect;
import cz.data.platform.web.listener.ApplicationReadyListener;
import cz.data.platform.web.utils.NacosDiscoveryServiceHeaderFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.*;

@Slf4j
@EnableCaching
@AutoConfigureOrder(0)
@PropertySource(value = "classpath:discovery-nacos.yml", factory = YamlPropertySourceFactory.class, encoding = "UTF-8")
public class DataPlatformAutoConfiguration {

    @Bean
    public ApplicationReadyListener applicationReadyListener() {
        return new ApplicationReadyListener();
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer defaultJackson2ObjectMapperBuilderCustomizer() {
        log.info("载入自定义ObjectMapper配置");
        return builder ->
                builder.modules(new ParameterNamesModule(), SecurityModuleUtil.simpleModule(), SecurityModuleUtil.javaTimeModule())
                        .mixIn(DataUser.class, DataUserMixin.class)
                        .mixIn(DataRole.class, DataRoleMixin.class)
                        .mixIn(DataGrantedAuthority.class, DataGrantedAuthorityMixin.class);
    }

    @Bean
    @ConditionalOnExpression("${api.log.enable:false}")
    public LogAspect logAspect(LogServiceFeign logServiceFeign) {
        return new LogAspect(logServiceFeign);
    }

    @Bean
    public DynamicRequestMatcherContainer defaultDynamicRequestMatcherContainer(
            RequestMappingHandlerMapping requestMappingHandlerMapping) {
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        Map<RequestMappingInfo, DataInner> dataInnerMap = new HashMap<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
            HandlerMethod handlerMethod = entry.getValue();
            Method method = handlerMethod.getMethod();
            DataInner annotation = method.getAnnotation(DataInner.class);
            if (annotation == null)
                continue;
            RequestMappingInfo mappingInfo = entry.getKey();
            dataInnerMap.put(mappingInfo, annotation);
        }
        List<DynamicRequestMatcherRule> matchers = new ArrayList<>();
        for (Map.Entry<RequestMappingInfo, DataInner> entry : dataInnerMap.entrySet()) {
            Set<RequestMethod> requestMethods = entry.getKey().getMethodsCondition().getMethods();
            String[] patternValues = entry.getKey().getPatternValues().toArray(new String[0]);
            for (RequestMethod requestMethod : requestMethods) {
                matchers.add(
                        new AnnotationMatcherRule(
                                HttpMethod.resolve(requestMethod.name()),
                                patternValues, entry.getValue().value())
                );
            }
        }
        matchers.forEach(m -> log.info("载入DataInner注解配置: {}-{} {} ", m.access(), m.method(), m.patterns()));
        matchers.add(new AnnotationMatcherRule(HttpMethod.GET,
                new String[]{"/doc.html", "/swagger-resources", "/v2/api-docs", "/webjars/js/**", "/webjars/css/**"},
                DataInner.Access.permitAll));
        return () -> matchers;
    }

    @Bean
    public DiscoveryServiceHeaderFactory nacosServiceHeaderFactory() {
        return new NacosDiscoveryServiceHeaderFactory();
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory() {
        return new OkHttp3ClientHttpRequestFactory();
    }
}
